﻿using System;
using System.Collections;
using System.Net;
using System.Collections.Generic;
using System.Security.Cryptography;

namespace NetWork.Packets.Factory
{
    public class PacketFactory
    {

        public const int ReceivePacketHead = 8;
        public const int WebSendPacketHead = 68;
        public const int TcpSendPacketHead = 28;

        public const int TcpCommunicationVersion = 1;
        public const int HttpCommunicationVersion = 10000;

        /// <summary>
        /// 长链接，PB To Bytes
        /// </summary>
        /// <param name="messageID">Opcode</param>
        /// <param name="data">pb data</param>
        /// <param name="type">server type</param>
        /// <param name="msg">result msg</param>
        /// <returns></returns>
		public static bool PB2Bytes(int messageID, object data, out byte[] msg)
        {
            byte[] aOpCode = ByteWriteInt(messageID);
            byte[] aData = ProtoBuf.Message.PBConvert.toBytes(messageID, data);
            if (aData == null)
            {
                msg = new byte[0];
                return false;
            }
            // pbData length
            byte[] aLength = ByteWriteInt(aData.Length);
            // communication verstion
            byte[] aCommunicationVersion = ByteWriteInt(TcpCommunicationVersion);
            // Pid
            byte[] aPID = null;
            aPID = ByteWriteLong(StaticData.playerData.playerId);

            byte[] aZoneId;
            aZoneId = ByteWriteInt(StaticData.playerData.ZoneId);
            // l
            byte[] aL = ByteWriteInt(TcpSendPacketHead + aData.Length - 4);
            msg = new byte[TcpSendPacketHead + aData.Length];

            Buffer.BlockCopy(aL, 0, msg, 0, 4);
            Buffer.BlockCopy(aCommunicationVersion, 0, msg, 4, 4);
            Buffer.BlockCopy(aOpCode, 0, msg, 8, 4);
            Buffer.BlockCopy(aZoneId, 0, msg, 12, 4);
            Buffer.BlockCopy(aPID, 0, msg, 16, 8);
            Buffer.BlockCopy(aLength, 0, msg, 24, 4);
            Buffer.BlockCopy(aData, 0, msg, TcpSendPacketHead, aData.Length);

            /*
            string str="";

            foreach (var it in aPID)
            {
                string binary = Convert.ToString( it, 2 ).PadLeft( 8, '0' );
                str+=binary+" ";
            }

            UnityEngine.Debug.Log( "[DEBUG] PID data:"+ str );

            str="";
            foreach( var it in msg )
            {
                string binary = Convert.ToString( it, 2 ).PadLeft( 8, '0' );
                str+=binary+" ";
            }
            UnityEngine.Debug.Log( "[DEBUG] packet data:"+ str );
			*/

            return true;
        }

        /// <summary>
        /// 长链接，Bytes To PB
        /// </summary>
        /// <param name="msg">bytes data</param>
        /// <param name="nOpCode">out opcode</param>
        /// <param name="obj">out pb obj</param>
        /// <returns></returns>
        public static bool Bytes2PB(byte[] msg, int length, out int nOpCode, out object obj)
        {
            if (msg.Length < 4)
            {
                nOpCode = -1;
                obj = null;
                return false;
            }
            nOpCode = ByteGetInt(msg, 0);
            try
            {
                obj = ProtoBuf.Message.PBConvert.toObject(nOpCode, msg, 4, length);
                return true;
            }
            catch (System.Exception e)
            {
                UnityEngine.Debug.LogError("msgLength:" + length + " , opcode:" + nOpCode + ", error:" + e);
                obj = null;
                return false;
            }
        }


        /// <summary>
        /// 短链接，PB To Bytes
        /// </summary>
        /// <param name="messageID">opcode</param>
        /// <param name="data">pb obj</param>
        /// <param name="msg">result msg</param>
        /// <returns></returns>
        public static bool WebPB2Bytes(int messageID, object data, out byte[] msg)
        {
            // Opcode
            byte[] aOpCode = ByteWriteInt(messageID);
            // Pb Data
            byte[] aData = ProtoBuf.Message.PBConvert.toBytes(messageID, data);
            if (aData == null)
            {
                msg = new byte[0];
                return false;
            }
            // Pb Data Length
            byte[] aLength = ByteWriteInt(aData.Length);
            // Sid
            int ZoneID;
            byte[] aZoneID;
            ZoneID = StaticData.playerData.ZoneId;
            aZoneID = ByteWriteInt(ZoneID);
            // Pid
            long PID;
            byte[] aPID;
            PID = StaticData.playerData.playerId;
            aPID = ByteWriteLong(PID);

            // Session Key
            byte[] aPlayerLoginKey = StaticData.sessionKey != null ? StaticData.sessionKey : ByteWriteString(" ", 16);
            // l
            byte[] aL = ByteWriteInt(WebSendPacketHead + aData.Length);
            // communicationVersion
            byte[] aCV = ByteWriteInt(HttpCommunicationVersion);
            // time
            long serverTime = ServerTimerTool.CurrentUTCJavaTime();
            byte[] time = ByteWriteLong(serverTime);

            // Sig
            byte[] aRandomKey = GetSig(PID, ZoneID, aData, serverTime);

            // build the net message to byte array
            msg = new byte[WebSendPacketHead + aData.Length];
            Buffer.BlockCopy(aL, 0, msg, 0, 4);
            Buffer.BlockCopy(aCV, 0, msg, 4, 4);
            Buffer.BlockCopy(aOpCode, 0, msg, 8, 4);
            Buffer.BlockCopy(aPID, 0, msg, 12, 8);
            Buffer.BlockCopy(aZoneID, 0, msg, 20, 4);
            Buffer.BlockCopy(aLength, 0, msg, 24, 4);
            Buffer.BlockCopy(aPlayerLoginKey, 0, msg, 28, 16);
            Buffer.BlockCopy(aRandomKey, 0, msg, 44, 16);
            Buffer.BlockCopy(time, 0, msg, 60, 8);
            Buffer.BlockCopy(aData, 0, msg, 68, aData.Length);
            return true;
        }

        /// <summary>
        /// 短链接，Bytes to PB 
        /// </summary>
        /// <param name="msg">Bytes</param>
        /// <param name="nOpCode">out opcode</param>
        /// <param name="obj">out pb obj</param>
        /// <returns></returns>
        public static bool WebBytes2PB(byte[] msg, out int nOpCode, out object obj, out List<Packet> externalPackets)
        {
            byte[] aMsg = new byte[msg.Length];
            Buffer.BlockCopy(msg, 0, aMsg, 0, msg.Length);


            int begin = 0;
            obj = null;
            nOpCode = -1;
            externalPackets = new List<Packet>();
            do
            {
                if (begin + ReceivePacketHead <= msg.Length)
                {
                    int pbLength = ByteGetInt(aMsg, begin);
                    int opcode = ByteGetInt(aMsg, begin + 4);
                    if (begin + ReceivePacketHead + pbLength <= msg.Length)
                    {
                        byte[] pbData = new byte[pbLength];
                        Buffer.BlockCopy(aMsg, begin + ReceivePacketHead, pbData, 0, pbLength);
                        var pb = ProtoBuf.Message.PBConvert.toObject(opcode, pbData);
                        if (obj == null)
                        {
                            obj = pb;
                            nOpCode = opcode;
                        }
                        else
                        {
                            Packet pack = new Packet(opcode, pb);
                            externalPackets.Add(pack);
                        }
                        begin = begin + ReceivePacketHead + pbLength;
                    }
                    else
                    {
                        break;
                    }
                }
                else
                {
                    break;
                }

            } while (true);

            if (obj != null)
            {
                return true;
            }
            return false;


            /*
            if (msg.Length < ReceivePacketHead)
            {
                nOpCode = -1;
                obj = null;
                return false;
            }
            //message copy
            //get message length
            int nLength = ByteGetInt(aMsg, 0);
            //get pb opcode
            nOpCode = ByteGetInt(aMsg, 4);
            //get pb data
            byte[] aData = new byte[nLength];
            Buffer.BlockCopy(aMsg, ReceivePacketHead, aData, 0, nLength);
            // translate the net message from byte array to an object
            
            if (nLength <= msg.Length - ReceivePacketHead)
            {
                obj = ProtoBuf.Message.PBConvert.toObject(nOpCode, aData);
                return true;
            }
            obj = null;
            return false;
            */
        }

        public static int GetMsgLength(byte[] msg)
        {
            return ByteGetInt(msg, 0);
        }

        #region To Byte[]
        private static byte[] ByteWriteShort(short s)
        {
            short destValue = IPAddress.HostToNetworkOrder(s);
            byte[] arrByte = BitConverter.GetBytes(destValue);
            //java与C#大小端不一，因此需进行反转
            return arrByte;
        }
        private static byte[] ByteWriteInt(int n)
        {
            int destValue = IPAddress.HostToNetworkOrder(n);
            byte[] arrByte = BitConverter.GetBytes(destValue);

            return arrByte;
        }
        private static byte[] ByteWriteLong(long l)
        {
            long destValue = IPAddress.HostToNetworkOrder(l);
            byte[] arrByte = BitConverter.GetBytes(destValue);
            return arrByte;
        }
        #endregion

        #region Byte[] To
        private static short ByteGetShort(byte[] msg, int nBegin)
        {
            byte[] arrByte = new byte[2];
            Array.Copy(msg, nBegin, arrByte, 0, 2);
            short num = BitConverter.ToInt16(arrByte, 0);
            return IPAddress.NetworkToHostOrder(num);
        }
        private static int ByteGetInt(byte[] msg, int nBegin)
        {
            int num = BitConverter.ToInt32(msg, nBegin);
            return IPAddress.NetworkToHostOrder(num);
        }
        private static long ByteGetLong(byte[] msg, int nBegin)
        {
            byte[] arrByte = new byte[8];
            Array.Copy(msg, nBegin, arrByte, 0, 8);
            long num = BitConverter.ToInt64(arrByte, 0);
            return IPAddress.NetworkToHostOrder(num);
        }
        #endregion

        private static byte[] ByteWriteString(string s, int length = 0)
        {
            byte[] result = new byte[length];
            byte[] arrByte = System.Text.Encoding.ASCII.GetBytes(s);
            if (length == 0)
            {
                return arrByte;
            }
            Array.Copy(arrByte, 0, result, 0, arrByte.Length);
            return result;
        }

        private static byte[] GetSig(long pid, int sid, byte[] data, long time)
        {
            string str = "" + pid + sid + time + "sao2016@wyht";
            byte[] preByte = System.Text.Encoding.ASCII.GetBytes(str);
            byte[] buffer = new byte[preByte.Length + data.Length];
            Buffer.BlockCopy(preByte, 0, buffer, 0, preByte.Length);
            Buffer.BlockCopy(data, 0, buffer, preByte.Length, data.Length);
            var a = GetMD5(buffer);
            return a;
        }

        private static byte[] GetMD5(byte[] s)
        {
            MD5 md5 = MD5.Create();
            byte[] bytes = md5.ComputeHash(s);
            return bytes;
        }
    }
}